public with sharing class LoanController 
{
    public static final String REFERRER_CODE = 'referrerCode';
    
    private static final String COMMISSION_BREAKDOWN_STANDARD = 'Standard';
    
    private static final String FROM_EMAIL_ADDRESS = 'info@refundsdirect.com.au';
    
    public static final Integer COOKIE_EXPIRY_SECONDS = 60 * 60 * 24 * 100;
    
    private Opportunity saveRequest;
    
    public LoanRequest loanRequest {get;set;}
    
    public PaymentDetails payDetails {get;set;}
    
    public LoanController()
    {   
        this.loanRequest = new loanRequest();
        this.payDetails = new PaymentDetails();
        String referrerCode = Apexpages.currentPage().getParameters().get(REFERRER_CODE);
        System.debug('Constructor referrerCode -> ' + this.loanRequest.referrerCode);
        Cookie referrerCookie;
        if(CommonUtils.isBlank(referrerCode))
        {
            referrerCookie = ApexPages.currentPage().getCookies().get(REFERRER_CODE);
            if(referrerCookie != null)
            {
                referrerCode = referrerCookie.getValue();
            }
        }
        if(referrerCode != null)
        {
            try
            {
                
                Account franchise = getFranchise(referrerCode);
                if(franchise != null)
                {
                    this.loanRequest.referrerCode = referrerCode;
                    referrerCookie =  new Cookie(REFERRER_CODE,referrerCode,null,COOKIE_EXPIRY_SECONDS,false);
                    ApexPages.currentPage().setCookies(new Cookie[]{referrerCookie});
                    
                }  
            }
            catch(System.Exception e)
            {
                System.debug(e.getMessage());
            }
        }
    }
    
    public static Account getFranchise(String referrerCode)
    {
        
        Account franchise = null;
        if(referrerCode != null)
        {
            try
            {
                RecordType recordType = [Select id From RecordType Where Name = 'Franchisee' limit 1];
                List<SelectOption>selectOptions = new List<SelectOption>();
                franchise = [select id, name, referrerCode__c from Account where account.RecordTypeId = :recordType.id and referrerCode__c = :referrerCode LIMIT 1];
            }
            catch(System.Exception e)
            {
              System.debug(e.getMessage());
            }
        }
        return franchise;
    }
    
    
    public PageReference eventApplicationStepOne()
    {
        validatePersonalDetails();
        if(this.loanRequest.getHasValidationErrors())
        {
            return null;
        }
        return Page.apply1;
    }
    
    public PageReference eventApplicationStepOneBack()
    {
        return Page.apply;
    }
    
    public PageReference eventCalculateRefunds()
    {
        validateLoanCalculation(true);
        if(this.loanRequest.getHasValidationErrors())
        {
            return null;
        }
        calculateLoan();
        //This will be the select lender page
        return Page.refundSummary;
    }
    
    public PageReference eventQuickCalculateRefunds()
    {
        validateLoanCalculation(false);
        if(this.loanRequest.getHasValidationErrors())
        {
            return null;
        }
        calculateLoan();
        this.loanRequest.submitted = true;
        //This will be the select lender page
        return null;
    }
    
    public PageReference eventApplicationStepTwoBack()
    {
        return Page.apply1;
    }
    
    
    private void calculateLoan()
    {
        try
        {
            
            Loan loan;
            commissionBreakdown__c commissionBreakdown;
            //Grab the commission breakdown that matches the referrercode
            //First  we have to getr  
            String commissionBreakdownType;
            try
            {
                Account account = [Select id, recordTypeId from Account where referrerCode__c = :this.loanRequest.referrerCode limit 1];
                commissionBreakdownType = account.recordTypeId;
                RecordType recordType = [Select id, name From RecordType Where id = :commissionBreakdownType limit 1];
                commissionBreakdownType = recordType.name;
                commissionBreakdown = [Select id, name, percentageToCustomer__c, percentageToRd__c, 
                    percentageToFranchisee__c from commissionBreakdown__c where Name = :commissionBreakdownType limit 1];
            }
            catch(System.Exception e)
            {
                System.debug('No commission breakdown exists for referrer code: ' + this.loanRequest.referrerCode);
                //Reset commission breakdown as none exists except standard type
                commissionBreakdownType  = COMMISSION_BREAKDOWN_STANDARD;
                commissionBreakdown = [Select id, name, percentageToCustomer__c, percentageToRd__c, 
                    percentageToFranchisee__c from commissionBreakdown__c where Name = :commissionBreakdownType limit 1];
            } 
            //Get the lenders that have a loan matching the loan type requested
            List<lenderLoan__c> loans = [Select id, name, commissionPercentage__c, lender__r.name,  lender__r.id, lender__r.isActive__c from lenderLoan__c where 
                loanType__c = :this.loanRequest.loanType and lender__r.isActive__c = true order by commissionPercentage__c desc];
            Set<String>imageIds = new Set<String>();
            for(lenderLoan__c iter: loans)
            {
                imageIds.add(iter.lender__r.id);
            }
            List<Attachment> images = [Select id, Name, ParentID from Attachment where ParentID in :imageIds];
            Map<String, Attachment> imageIdMap = new Map<String, Attachment>();
            for(Attachment image : images )
            {
                imageIdMap.put(image.parentID, image);
            } 
            List<Loan>calculatedLoans = new List<Loan>();
            //The counter is used to generate unique ids on the
            //refundSummary page for the qtip functionality
            Integer ct = 0;
            for(LenderLoan__c lenderLoan : loans)
            {   
                //calculate the amount
                loan = new Loan();
                loan.index = ct++;
                loan.selected = false;
                loan.request = new loanRequest__c();
                loan.request.lenderLoan__c = lenderLoan.id;
                loan.lender = lenderLoan;
                loan.image = imageIdMap.get(lenderLoan.lender__r.id);
                //Now calculate - the commissions based on the lender loan, amount and commission breakdown
                calculateRefundPercentages(commissionBreakdown, lenderLoan, loan.request);
                calculatedLoans.add(loan);
                loan.setStyleClass('');
                System.assert(loan.getStyleClass() != null);
                
            }
            //Now we have to check against any current loans to set if they're selected or not
            for(Loan existingLoan :this.loanRequest.loans)
            {
                if(existingLoan.selected)
                {
                    for(Loan calculatedLoan : calculatedLoans)
                    {
                        if(existingLoan.lender.id == calculatedLoan.lender.id)
                        {
                            calculatedLoan.selected = true;
                        }
                    }
                }
            }
            this.loanRequest.loans = calculatedLoans;
        }
        catch(System.Exception e)
        {
            System.debug(e.getMessage());
        }
    }
    
    
    public PageReference eventValidateChosenLenders()
    {
        loanRequest.errors.remove('selectedLoans');
        Integer ct = 0;
        for(Loan loan : loanRequest.loans)
        {
            if(loan.selected)
            {
                ct++;
            }
        }
        if(ct < 1 || ct > 3)
        {
            loanRequest.errors.add('selectedLoans');
            return null;
        }
        return Page.apply3;
    }
    
    public PageReference eventApplicationStepThreeBack()
    {
        return Page.refundSummary;
    }
    
    /**
    * calculateRefundPercentages the refund percentages
    */
    private void calculateRefundPercentages(commissionBreakdown__c commissionBreakdown, LenderLoan__c lenderLoan, loanRequest__c loanRequest)
    {
        Double amount = Double.valueOf(this.loanRequest.amount);
        Double amountAvailableForRefund = (amount / 100) * lenderLoan.commissionPercentage__c; 
        //Now we have to figure out the percentages going to customer/rf
        loanRequest.commissionToCustomer__c = (amountAvailableForRefund /100) * commissionBreakdown.percentageToCustomer__c;
        loanRequest.commissionToRd__c = (amountAvailableForRefund /100) * commissionBreakdown.percentageToRd__c;
        if(commissionBreakdown.percentageToFranchisee__c > 0)
        {
            loanRequest.commissionToFranchise__c = (amountAvailableForRefund /100) * commissionBreakdown.percentageToFranchisee__c;
        }
    }
    
    public PageReference eventSubmitLoanRequest()
    {
        validateLoanRequest();
        if(this.loanRequest.getHasValidationErrors())
        {
            return null;
        }
        //Pressed from confirmation page
        try
        {
            Account account;
            RecordType recordType = getRecordType('Customer', true);  
            if(!isRegistered(this.loanRequest.email))
            {   
                account = new Account(RecordTypeId = recordType.id);
                account.Salutation = this.loanRequest.salutation;
                account.FirstName = this.loanRequest.firstname;
                account.LastName = this.loanRequest.lastname;
                account.BillingStreet = this.loanRequest.address1;
                account.BillingCity = this.loanRequest.suburb;
                account.BillingCountry = this.loanRequest.country;
                account.BillingState = this.loanRequest.state;
                account.BillingPostalCode = this.loanRequest.postcode;
                account.PersonEmail = this.loanRequest.email;
                account.PersonHomePhone = this.loanRequest.phone;
                Account franchise = loanRequest.getFranchise();
                if(franchise!= null)
                {
                    account.referrerName__c = franchise.name;
                }
                setObjectOwnerId(account);
                insert account;
            }
            //Now load account again as we need the regardless of whether we just inserted a new account ContactID 
            account = 
                [Select id, PersonContactID, Name from Account where RecordTypeId = :recordType.id and PersonEmail = :this.loanRequest.email and IsPersonAccount = true LIMIT 1];
            //Now we want to ensure that we don't get duel form submissions
            try
            {
                List<Opportunity>opportunities = [Select id, name, loanAmount__c, CreatedDate from Opportunity where AccountID= :account.id and CreatedDate  = TODAY];
                for(Opportunity opp : opportunities)
                {
                    if(opp.loanAmount__c == this.loanRequest.getAmountAsDouble())
                    {
                        loanRequest.errors.add('loanSubmitted');
                        return null;
                    }
                }
            }
            catch(System.Exception e)
            {
                System.debug('No opportunities created for this account');
            }
            //Now create the opp and loan requests.
            saveRequest = new Opportunity();
            saveRequest.AccountID = account.id;
            //opportunity.isPropertyForSecurity__c = (this.loanRequest.securityOffered != null || this.loanRequest.securityOfferedOther != null);
            saveRequest.Amount = this.loanRequest.getAmountAsDouble();
            saveRequest.name = 'Loan Request for ' + 
                this.loanRequest.firstName + ' ' + this.loanRequest.lastName;
            saveRequest.StageName = 'pending';
            saveRequest.CloseDate = System.today() + 28;
            saveRequest.additionalInformation__c = this.loanRequest.additionalInfo;
            saveRequest.loanDuration__c = this.loanRequest.duration;
            saveRequest.contactTime__c = this.loanRequest.preferredContactTime;
            saveRequest.loanType__c = this.loanRequest.loanType;
            saveRequest.referredBy__c = this.loanRequest.referredBy;
            //Now we have to build a display string of lender names for the email template
            setObjectOwnerId(saveRequest);
            insert saveRequest;
            //Now create the loan request objects
            String lenderNames = '';
            List<loanRequest__c>loanRequests = new List<loanRequest__c>();
            for(Loan loan : this.loanRequest.loans)
            {
                if(loan.selected)
                {
                    lenderNames += loan.lender.lender__r.name + ',';
                    //We have already created the loan requests in the calculate method
                    //we now only want to persist the selected ones.
                    loan.request.status__c = 'pending';
                    System.debug('Setting loan request name: ' + loan.lender.lender__r.name);
                    loan.request.name = loan.lender.lender__r.name;
                    loan.request.opportunity__c = saveRequest.id;
                    loan.request.status__c = 'submitted';
                    loanRequests.add(loan.request);
                    //Send out the lender ameil
                }    
            }
            insert loanRequests;
            //Send out the customer email
            saveRequest.lenderNames__c  = lenderNames.subString(0, lenderNames.lastIndexOf(','));
            update saveRequest;
            //Send all the emails out
            Contact contact = [Select id, name, email from Contact where id = :account.PersonContactId];
            sendEmail(contact, 'refundsDirectBorrowersEmail');
            for(Loan loan : this.loanRequest.loans)
            {
                if(loan.selected)
                {
                    try
                    {
                        contact = [SELECT id, name, email from Contact where AccountID = :loan.lender.lender__c LIMIT 1];
                        sendEmail(contact, 'refundsDirectLendersEmail');
                        
                    }
                    catch(System.Exception e)
                    {
                        //No associated contact
                        System.debug(e.getMessage());
                    }
                }
            }
            saveRequest.confirmationOfSubmissionToCustomer__c = System.today();
            saveRequest.dateLendersNotified__c = System.today();
            update saveRequest;
            
        }
        catch(System.Exception e)
        {
            System.debug(e.getMessage());
            loanRequest.errors.add('systemError');
            return null;
        }
        //Now create the payment details object
        return Page.applicationSummary;
    }
    
    /**
    * Sends out the email to the appropriate email address using the passed in template
    */
    private void sendEmail(Contact contact, String emailTemplateName)
    {
        System.debug('Template name: ' + emailTemplateName);
        if(CommonUtils.isNotBlank(contact.email))
        {
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            EmailTemplate et = [SELECT id FROM EmailTemplate WHERE DeveloperName = :emailTemplateName];
            mail.setSenderDisplayName('Refunds Direct');
            mail.setTargetObjectId(contact.id);
            mail.setSenderDisplayName(FROM_EMAIL_ADDRESS);
            mail.setReplyTo(FROM_EMAIL_ADDRESS);
            //mail.setTargetObjectId(saveRequest.id); // Specify who the email should be sent to.
            mail.setTemplateId(et.id);
            mail.setWhatId(saveRequest.id);
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
        }
    }
    
    public List<SelectOption> getLoanTypes()
    {
        List<SelectOption> options = new List<SelectOption>();
        Schema.DescribeFieldResult fieldResult =
                 LenderLoan__c.loanType__c.getDescribe();
        List<Schema.PicklistEntry> picklistEntries = fieldResult.getPicklistValues();    
        options.add(new SelectOption('', 'Please select.....'));
        for( Schema.PicklistEntry pickListEntry : picklistEntries)
        {
           options.add(new SelectOption(pickListEntry.getValue(), pickListEntry.getValue()));
        }       
        return options;
    }
    
    public List<SelectOption> getPreferredContactTimes()
    {
        List<SelectOption> options = new List<SelectOption>();
        options.add(new SelectOption('', 'Please select.....'));
        options.add(new SelectOption('Anytime', 'Anytime'));
        options.add(new SelectOption('7am - 9am', '7am - 9am'));
        options.add(new SelectOption('9am - 12pm', '9am - 12pm'));
        options.add(new SelectOption('12pm - 3pm', '12pm - 3pm'));
        options.add(new SelectOption('3pm - 6pm', '3pm - 6pm'));
        options.add(new SelectOption('6pm - 9pm', '6pm - 9pm'));
        return options;
    }
    
    public List<SelectOption> getLoanDurations()
    {
        List<SelectOption> options = new List<SelectOption>();
        Schema.DescribeFieldResult fieldResult =
                 Opportunity.loanDuration__c.getDescribe();
        List<Schema.PicklistEntry> picklistEntries = fieldResult.getPicklistValues();    
        options.add(new SelectOption('', 'Please select.....'));
        for( Schema.PicklistEntry pickListEntry : picklistEntries)
        {
           options.add(new SelectOption(pickListEntry.getValue(), pickListEntry.getValue()));
        }       
        return options;
    }
    
    public boolean isRegistered(String email)
    {
        try
        {
            RecordType recordType = getRecordType('Customer', true);
            Account account = 
                [Select id from Account where RecordTypeId = :recordType.id and PersonEmail = :email and IsPersonAccount = true LIMIT 1];
            return true;
        }
        catch(Exception e)
        {
            System.debug(e.getMessage());
            return false;
        }
    }
    
    public RecordType getRecordType(String recordTypeName, Boolean isPersonAccount)
    {
        
        RecordType recordType = null;
        try
        {
            recordType = 
                [Select Id, Name From RecordType Where SobjectType = 'Account' and Name = :recordTypeName AND IsPersonType=:isPersonAccount LIMIT 1];
            System.debug('Record Type --> '  + recordType.Name);
        }
        catch(Exception e)
        {
            System.debug(e.getMessage());
        }
        return recordType;
         
    }
    
    /**
    * Try and get the appropriate parent account based on the refferer code.  If none exists or the refferrer code
    * is null then we don't set the parent account meaning it's a refund direct customer.
    */
    private void setObjectOwnerId(SObject objectReference)
    {
    	System.debug('setObjectOwnerId referrerCode -> ' + this.loanRequest.referrerCode);
        if(CommonUtils.isNotBlank(this.loanRequest.referrerCode))
        {
            try
            {
                Account parentAccount = [Select id from Account where referrerCode__c = :this.loanRequest.referrerCode];
                if(parentAccount != null)
                {
                    //Now we have to figure out the partner user id associated with account
                    Contact contact = [Select id, name from Contact where AccountID = :parentAccount.id limit 1];
                    User user = [Select id from User where ContactID = :contact.id and IsActive = true];
                    objectReference.put('OwnerId', user.id);
                }
            }
            catch(System.Exception e)
            {
                System.debug(e.getMessage());
            }
        }
    }
    
    private void validatePersonalDetails()
    {
        loanRequest.errors = new Set<String>();
        if(CommonUtils.isBlank(loanRequest.firstName))
        {
            this.loanRequest.errors.add('firstName');   
        }
        if(CommonUtils.isBlank(loanRequest.lastName))
        {
            this.loanRequest.errors.add('lastName');
        }
         if(CommonUtils.isBlank(loanRequest.email))
        {
            this.loanRequest.errors.add('email');
        }
        else if(!CommonUtils.isValidEmailFormat(loanRequest.email))
        {
             this.loanRequest.errors.add('email');
        }
        if(CommonUtils.isBlank(loanRequest.phone))
        {
            this.loanRequest.errors.add('phone');
        }
        else if(!CommonUtils.isValidPhoneNumber(loanRequest.phone))
        {
            this.loanRequest.errors.add('phone');
        }
        if(CommonUtils.isBlank(loanRequest.postcode))
        {
            this.loanRequest.errors.add('postcode');
        }
        else if(!CommonUtils.isValidPostcode(loanRequest.postcode))
        {
            this.loanRequest.errors.add('postcode');
        }
    }
    
    private void validateLoanCalculation(boolean validateDuration)
    {
    
        loanRequest.errors = new Set<String>();
        if(CommonUtils.isBlank(loanRequest.loanType))
        {
            loanRequest.errors.add('loanType');
        }
        if(CommonUtils.isBlank(loanRequest.amount))
        {
            loanRequest.errors.add('amount');
        }
        else 
        {
        	//Strip non numeric chars from number
        	String stripped = loanRequest.amount.replaceAll('\\D','');
        	loanRequest.amount = stripped;
	        if(!CommonUtils.isValidNumber(loanRequest.amount))
	        {
	            loanRequest.errors.add('amount');
	        }
        }
        if(validateDuration)
        {
            if(CommonUtils.isBlank(loanRequest.duration))
            {
                this.loanRequest.errors.add('duration');
            }
        }
    }
    
    private void validateLoanRequest()
    {
    
        loanRequest.errors = new Set<String>();
        if(!loanRequest.permissionToSubmit)
        {
            this.loanRequest.errors.add('permissionToSubmit');
        }
        if(!loanRequest.getHasSelectedLenders())
        {
            this.loanRequest.errors.add('noSelectedLenders');
        }    
    }

}